#!/usr/bin/env bash
# Copyright (c) 2021 maminjie <canpool@163.com>
# SPDX-License-Identifier: MulanPSL-2.0

method_def patcher

__PATCHER_PY="${BASH_SOURCE[0]%/*}/patcher.py"

usage_patcher() {
printf "patcher (pat): Analyse patch files in source code

usage:
    ${PROG} patcher subcmd [options] <args...>

subcmd:
$(__patcher_usage_commands)

notes:
    Type '${PROG} patcher help <subcmd>' for help on a specific subcommand.

"
}

alias_def patcher pat
do_patcher() {
    local subcmd=$(__patcher_get_subcmd "$1")
    if [ -z "$subcmd" ]; then
        usage_patcher; exit
    else
        shift; patcher_do_$subcmd "$@"
    fi
}

__patcher_get_subcmd() {
    local cmd=""
    case "${1}" in
        "h"|"help")
            cmd="help"
            ;;
        "c"|"check")
            cmd="check"
            ;;
        "g"|"group")
            cmd="group"
            ;;
        "i"|"info")
            cmd="info"
            ;;
        "d"|"diff")
            cmd="diff"
            ;;
        "s"|"stat")
            cmd="stat"
            ;;
        *)
            ;;
    esac
    echo "$cmd"
}

__patcher_usage_commands() {
    local usages=$(declare_functions | grep -E "^patcher_usage_[a-zA-Z0-9_]+")
    for usage in $usages; do
        eval local usageinfo=\"$(${usage} | head -n 1)\"
        printf "    %s\n" "$usageinfo"
    done | format_column ':'
}


patcher_usage_help() {
printf "help (h): Show the specific subcommand usage

usage:
    ${PROG} patcher h subcmd

"
}

# patcher_do_help subcmd
patcher_do_help() {
    if [ $# -ne 1 ]; then
        patcher_usage_help; exit
    fi
    local subcmd=$(__patcher_get_subcmd "$1")
    local usage="patcher_usage_$subcmd"
    if [ "$(type -t "$usage")" = "function" ] ; then
        $usage
    else
        echo "Unknown command '$1'"
    fi
}

patcher_usage_check() {
printf "check (c): Check differences between patch and source code

usage:
    ${PROG} patcher c PATCH SOURCE

params:
    PATCH - patch file or directory
    SOURCE - source code directory

"
}

patcher_do_check() {
    if [ $# -ne 2 ]; then
        patcher_usage_check; exit
    fi
    python3 $__PATCHER_PY -c check -p "$1" -s "$2" | format_column ","
}

patcher_usage_group() {
printf "group (g): Group patches by modified files

usage:
    ${PROG} patcher g DIR

params:
    DIR - patch directory

"
}

patcher_do_group() {
    if [ $# -ne 1 ]; then
        patcher_usage_group; exit
    fi
    python3 $__PATCHER_PY -c group -p "$1"
}

patcher_usage_info() {
printf "info (i): Show informations of patch file(s)

usage:
    ${PROG} patcher i PATCH

params:
    PATCH - patch file or directory

file flags:
    - means to delete file
    + means to add file
    * means to modify file

"
}

patcher_do_info() {
    if [ $# -ne 1 ]; then
        patcher_usage_info; exit
    fi
    python3 $__PATCHER_PY -c info -p "$1"
}


patcher_usage_diff() {
printf "diff (d): Diff patch files

usage:
    ${PROG} patcher d file1 file2
    ${PROG} patcher d dir1 dir2
\n"
}

# __patcher_do_diff dir1 dir2
__patcher_do_diff() {
    local src_patches=$(ls $1/*.patch 2> /dev/null)
    local dst_patches=$(ls $2/*.patch 2> /dev/null)
    declare -A src_dict
    declare -A dst_dict
    local name=""

    for f in $src_patches; do
        name=${f##*/}; name=${name#*-}; name=$(string_lower $name)
        if [ -n "$name" ]; then
            dict_add src_dict "$name" "$f"
        fi
    done
    for f in $dst_patches; do
        name=${f##*/}; name=${name#*-}; name=$(string_lower $name)
        if [ -n "$name" ]; then
            dict_add dst_dict "$name" "$f"
        fi
    done

    local src_keys=$(dict_keys src_dict)
    local pdf=".pdf"
    [ -f "$pdf" ] && rm -f "$pdf"
    for k in $src_keys; do
        local spatch=$(dict_get src_dict "$k")
        local dpatch=$(dict_get dst_dict "$k")
        if [ -z "$dpatch" ]; then
	    echo "$spatch|" >> $pdf
	else
	    echo "$spatch|$dpatch" >> $pdf
	    dict_remove dst_dict "$k"
	fi
    done
    local dfiles=$(sort $pdf)
    [ -f "$pdf" ] && rm -f "$pdf"
    local i=1
    local dcnt=0
    for f in $dfiles; do
        local spatch=$(echo "$f" | awk -F '|' '{print $1}')
        local dpatch=$(echo "$f" | awk -F '|' '{print $2}')
        if [ -z "$dpatch" ]; then
            echo "$i) -only $spatch"
            ((dcnt++))
        else
            local res=$(diff -u "$spatch" "$dpatch")
            if [ -z "$res" ]; then
                echo "$i) equal $spatch $dpatch"
            else
                local resdiff=$(echo "$res" | grep -vE "\.{10}T")
                if [ -z "$resdiff" ]; then
                    echo "$i) equal $spatch $dpatch"
                else
                    local resdiff=$(echo "$res" |\
                        grep -Ev "^\-\-\-|^\+\+\+|^(\-|\+)(Subject:|From |index|@@|$)" |\
                        grep -E "^\-|^\+")
                    if [ -z "$resdiff" ]; then
                        echo "$i) equal $spatch $dpatch"
                    else
                        echo "$i) diff $spatch $dpatch"
                        echo "$res"
                        ((dcnt++))
                    fi
                fi
            fi
            dict_remove dst_dict "$k"
        fi
        ((i++))
    done
    local dst_values=$(dict_values dst_dict)
    for v in $dst_values; do
        echo "$i) +only $v"
        ((dcnt++))
        ((i++))
    done
    echo ""
    echo "There are about $dcnt differences..."
}

# patcher_do_diff file1 file2
# patcher_do_diff dir1 dir2
patcher_do_diff() {
    if [ $# -ne 2 ]; then
        patcher_usage_diff; exit
    fi
    if [[ -f "$1" && -f "$2" ]]; then
        diff -u $1 $2
    elif [[ -d "$1" && -d "$2" ]]; then
        __patcher_do_diff $1 $2
    else
        patcher_usage_diff; exit
    fi
}

patcher_usage_stat() {
printf "stat (s): Statistics patch file modification information

usage:
    ${PROG} patcher s DIR
\n"
}

# patcher_do_stat DIR
patcher_do_stat() {
    local dir=$1
    if [ ! -d "$1" ]; then
        patcher_usage_stat; exit
    fi
    dir=$(realpath "$dir")
    local patches=$(ls -rc $dir/*.patch 2>/dev/null)
    echo "patch,stat,insertion(+),deletion(-),total"
    local f_total=0 i_total=0 d_total=0 m_total=0
    for patch in $patches; do
        local p_name=$(basename $patch)
        local stat=$(patch_stat "$patch")
        local changes=$(echo "$stat" | grep -E "file[s]? changed") # (file|files)
        stat=$(echo "$stat" | sed 's/"/""/g' | head -n50)
        local f_num=$(echo "$changes" | awk '{print $1}')
        [[ -z "f_num" ]] && f_num=0
        local insertion=$(echo "$changes" | awk '{print $4}')
        [[ -z "$insertion" ]] && insertion=0
        local deletion=$(echo "$changes" | awk '{print $6}')
        [[ -z "$deletion" ]] && deletion=0
        local total=0
        let "total = insertion + deletion"
        echo "$p_name,\"$stat\",$insertion,$deletion,$total"
        let "f_total += f_num"
        let "i_total += insertion"
        let "d_total += deletion"
        let "m_tatal += total"
    done
    echo "total,files($f_total),insertions($i_total),deletions($d_total),total($m_total)"
}
